using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace _2DGame
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class World : Microsoft.Xna.Framework.GameComponent
    {
        Background background;
        Map map;

        public class ObjectList
        {
            public List<GameObject> objects = new List<GameObject>();
            public List<GameObject> nonCollisionObjects = new List<GameObject>();
            public List<EffectObject> effects = new List<EffectObject>();

            public void Add(DrawObject newObject)
            {
                if (newObject is EffectObject)
                {
                    effects.Add(newObject as EffectObject);
                }
                else
                if(newObject is GameObject)
                {
                    GameObject newGameObject = newObject as GameObject;

                    if (newGameObject.CanBeCollidedWith)
                    {
                        objects.Add(newGameObject);
                    }
                    else
                    {
                        nonCollisionObjects.Add(newGameObject);
                    }
                }
            }

            public void Remove(DrawObject removedObject)
            {
                if (removedObject is EffectObject)
                {
                    effects.Remove(removedObject as EffectObject);
                }
                else
                    if (removedObject is GameObject)
                    {
                        GameObject removedGameObject = removedObject as GameObject;

                        if (removedGameObject.CanBeCollidedWith)
                        {
                            objects.Remove(removedGameObject);
                        }
                        else
                        {
                            nonCollisionObjects.Remove(removedGameObject);
                        }
                    }
            }
        }

        ObjectList worldObjects;
        List<DrawObject> newObjects;
        List<DrawObject> removedObjects;

        List<Player> playerList;

        List<PickupSpawnPoint> pickupPoints = new List<PickupSpawnPoint>();

        Dictionary<SpriteBlendMode, List<DrawObject>> renderLists = new Dictionary<SpriteBlendMode, List<DrawObject>>();

        Vector2 gravity = new Vector2(0.0f, 1.5f);

        Vector2 cameraPosition = new Vector2();
        float drawScale = 1.0f;

        public Vector2 Gravity
        {
            get { return gravity; }
        }

        public Vector2 CameraPosition
        {
            get { return cameraPosition; }
        }

        public float DrawScale
        {
            get { return drawScale; }
        }

        public Map Map
        {
            get { return map; }
        }

        public ObjectList WorldObjects
        {
            get { return worldObjects; }
        }

        public List<Player> PlayerList
        {
            get { return playerList; }
        }

        public World(Game game)
            : base(game)
        {
            // TODO: Construct any child components here
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            AddToTextureBank("dot_8");
            AddToTextureBank("white_pixel");
            AddToTextureBank("placeholder_bg_001");
            AddToTextureBank("placeholder_fg_001");
            AddToTextureBank("placeholder_level_001");
            AddToTextureBank("Levels//level_test_02");
            AddToTextureBank("Levels//level_test_02_col");
            AddToTextureBank("placeholder_level_001");
            AddToTextureBank("Ships//ship_battlestar");
            AddToTextureBank("Ships//ship_cylon_raider");
            AddToTextureBank("Ships//fighter_ship");
            AddToTextureBank("bullet_small");
            AddToTextureBank("border_tile");
            AddToTextureBank("bomb_sheet");
            AddToTextureBank("missile");

            // TODO: Add your initialization code here
            background = new Background(2);
            background.SetLayer(0, "placeholder_bg_001");
            background.SetLayer(1, "placeholder_fg_001");
            map = new Map("Levels//level_test_02", "Levels//level_test_02_col", "border_tile");
            worldObjects = new ObjectList();
            newObjects = new List<DrawObject>();
            removedObjects = new List<DrawObject>();
            playerList = new List<Player>();

            renderLists.Add(SpriteBlendMode.Additive, new List<DrawObject>());
            renderLists.Add(SpriteBlendMode.AlphaBlend, new List<DrawObject>());

            base.Initialize();
        }

        private void AddToTextureBank(String filename)
        {
            Texture2D texture;

            if (Resources.TextureBank.TryGetValue(filename, out texture) == false)
            {
                // Textures will be loaded into the bank during World.LoadContent
                // And we'll make our textureData then
                Resources.TextureBank.Add(filename, null);
            }
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        public void LoadContent()
        {
            // We can't modify a dictionary we're iterating, so make a list of filenames to load
            List<String> texturesToLoad = new List<String>();
            foreach (KeyValuePair<String, Texture2D> keyValue in Resources.TextureBank)
            {
                texturesToLoad.Add(keyValue.Key);
            }
            foreach (String filename in texturesToLoad)
            {
                Resources.TextureBank[filename] = Resources.Content.Load<Texture2D>(filename);
            }

            // TODO: use this.Content to load your game content here
            background.LoadContent();
            map.LoadContent();
            foreach (DrawObject currentObject in newObjects)
            {
                currentObject.LoadContent();
            }
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // Defered new objects until we've finished iterating
            // Changing a list when it's being iterated through is a no-no
            foreach (DrawObject currentObject in newObjects)
            {
                worldObjects.Add(currentObject);
                renderLists[currentObject.BlendMode].Add(currentObject);
            }
            newObjects.Clear();

            background.Update(gameTime);
            map.Update(gameTime);
            foreach (GameObject currentObject in worldObjects.objects)
            {
                currentObject.Update(gameTime);
            }
            foreach (GameObject currentObject in worldObjects.nonCollisionObjects)
            {
                currentObject.Update(gameTime);
            }
            foreach (EffectObject currentEffect in worldObjects.effects)
            {
                currentEffect.Update(gameTime);
            }
            foreach (PickupSpawnPoint currentSpawnPoint in pickupPoints)
            {
                currentSpawnPoint.Update(gameTime);
            }

            // Defer destructions until we've finished iterating
            // Changing a list when it's being iterated through is a no-no
            foreach (DrawObject currentObject in removedObjects)
            {
                worldObjects.Remove(currentObject);
                renderLists[currentObject.BlendMode].Remove(currentObject);
            }
            removedObjects.Clear();

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public void Draw(Viewport drawViewport, Vector2 position)
        {
            cameraPosition = position;

            Resources.Device.Viewport = drawViewport;
            drawScale = ((drawViewport.Width / (float)Resources.MainViewport.Width) + (drawViewport.Height / (float)Resources.MainViewport.Height)) / 2.0f;

            // TODO: Add your drawing code here
            Resources.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
            Resources.Device.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            Resources.Device.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            background.Draw();
            map.Draw();
            foreach (DrawObject currentObject in renderLists[SpriteBlendMode.AlphaBlend])
            {
                currentObject.Draw();
            }

            Resources.SpriteBatch.End();

            // TODO: Add your drawing code here
            Resources.SpriteBatch.Begin(SpriteBlendMode.Additive, SpriteSortMode.Immediate, SaveStateMode.None);
            Resources.Device.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            Resources.Device.SamplerStates[0].AddressV = TextureAddressMode.Wrap;

            foreach (DrawObject currentObject in renderLists[SpriteBlendMode.Additive])
            {
                currentObject.Draw();
            }

            Resources.SpriteBatch.End();
        }

        public Point GetScreenPosition(Vector2 position)
        {
            Point screenPosition = new Point();

            screenPosition.X = (int)Math.Round(position.X - cameraPosition.X);
            screenPosition.Y = (int)Math.Round(position.Y - cameraPosition.Y);

            screenPosition.X = (int)(screenPosition.X * Resources.World.DrawScale);
            screenPosition.Y = (int)(screenPosition.Y * Resources.World.DrawScale);

            // Add half the screen size, so that if you're at 0,0 relative to the camera, that's the middle of the screen
            screenPosition.X += Resources.Device.Viewport.Width / 2;
            screenPosition.Y += Resources.Device.Viewport.Height / 2;

            return screenPosition;
        }

        public void ObjectMoved(GameObject movedObject)
        {
            List<Map.CollisionInfo> collisions;

            if (map.IsColliding(movedObject, out collisions))
            {
                movedObject.TouchMap(collisions);
            }
            foreach (GameObject currentObject in worldObjects.objects)
            {
                if (movedObject.IsColliding(currentObject))
                {
                    if (movedObject.Touch(currentObject) == false)
                    {
                        currentObject.Touch(movedObject);
                    }
                }
            }
        }

        public GameObject ObjectNear(Vector2 point, float radius)
        {
            float squareRadius = radius * radius;

            foreach (GameObject currentObject in Resources.World.WorldObjects.objects)
            {
                float squareDistance = (point - currentObject.Position).LengthSquared();

                if (squareDistance < squareRadius)
                {
                    return currentObject;
                }
            }

            foreach (DrawObject newObject in newObjects)
            {
                if(newObject is GameObject)
                {
                    GameObject newGameObject = newObject as GameObject;

                    if (newGameObject.CanBeCollidedWith)
                    {
                        float squareDistance = (point - newGameObject.Position).LengthSquared();

                        if (squareDistance < squareRadius)
                        {
                            return newGameObject;
                        }
                    }
                }
            }

            return null;
        }

        public void AddPlayer(Player newPlayer)
        {
            playerList.Add(newPlayer);
        }

        public void AddObject(DrawObject newObject)
        {
            newObjects.Add(newObject);
            newObject.InWorld = true;
        }

        public void RemoveObject(DrawObject removedObject)
        {
            removedObjects.Add(removedObject);
            removedObject.InWorld = false;
        }

        public void AddPickupSpawnPoint(Vector2 newPosition)
        {
            pickupPoints.Add(new PickupSpawnPoint(newPosition));
        }
    }
}